﻿using System;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Gamelight.Scene
{
	public partial class GameControl : UserControl
	{
		static GameControl()
		{
			HtmlPage.Document.AttachEvent("oncontextmenu", OnContextMenu);
		}

		static void OnContextMenu(object sender, HtmlEventArgs e)
		{
			e.PreventDefault();
		}

		private static GameControl instance = null;

		private GameSceneBase startingScene = null;
		public GameSceneBase StartingScene
		{
			get { return this.startingScene; }
			set
			{
				if (this.startingScene == null)
				{
					this.startingScene = value;
					this.startingScene.SoundManager = new Sound.SoundManager(this);
				}
				else
				{
					throw new InvalidOperationException("Cannot set the starting scene more than once");
				}
			}
		}

		public void StartGame()
		{
			this.InitializeGame();
		}

		public bool StretchUsingNearestNeighbor
		{
			get;
			set;
		}

		internal System.Windows.Controls.Image ImageLoaderControl { get; private set; }
		internal System.Windows.Controls.Grid SoundMixer { get; private set; }
		
		private bool IsReadyToInitialize
		{
			get
			{
				return this.width > 0 &&
					this.height > 0 &&
					this.targetFramesPerSecond > 0 &&
					this.startingScene != null &&
					this.activeScene == null;
			}
		}

		private GameSceneBase activeScene = null;
		internal GameSceneBase ActiveScene
		{
			get
			{
				return this.activeScene;
			}
			set
			{
				this.activeScene = value;
			}
		}

		internal Graphics.Image GameScreen { get; private set; }

		internal static GameControl Instance { get { return instance; } }

		public GameControl()
		{
			this.StretchUsingNearestNeighbor = false;
			this.FullScreenShortcutKey = Input.Key.None;

			if (instance != null)
			{
				throw new InvalidOperationException("Cannot create more than one instance of GameControl.");
			}
			instance = this;

			InitializeComponent();

			this.SoundMixer = this.mediaHost;

			this.Focus();

			Application.Current.Host.Content.FullScreenChanged += new EventHandler(Content_FullScreenChanged);

			this.MouseEnter += new MouseEventHandler(layoutRoot_MouseEnter);
			this.MouseLeave += new MouseEventHandler(layoutRoot_MouseLeave);
			this.MouseMove += new MouseEventHandler(layoutRoot_MouseMove);
			this.MouseLeftButtonDown += new MouseButtonEventHandler(layoutRoot_MouseLeftButtonDown);
			this.MouseLeftButtonUp += new MouseButtonEventHandler(layoutRoot_MouseLeftButtonUp);
			this.MouseRightButtonDown += new MouseButtonEventHandler(layoutRoot_MouseRightButtonDown);
			this.MouseRightButtonUp += new MouseButtonEventHandler(layoutRoot_MouseRightButtonUp);
			this.KeyDown += new KeyEventHandler(layoutRoot_KeyDown);
			this.KeyUp += new KeyEventHandler(layoutRoot_KeyUp);
		}

		private void Content_FullScreenChanged(object sender, EventArgs e)
		{
			double scaleX = 1;
			double scaleY = 1;
			if (this.IsFullScreen)
			{
				double fullscreenHeight = Application.Current.Host.Content.ActualHeight;
				double fullscreenWidth = Application.Current.Host.Content.ActualWidth;

				double actualWidth = (Application.Current.RootVisual as FrameworkElement).ActualWidth;
				double actualHeight = (Application.Current.RootVisual as FrameworkElement).ActualHeight;
				
				scaleX = fullscreenWidth / actualWidth;
				scaleY = fullscreenHeight / actualHeight;
			}

			this.RenderTransform = new ScaleTransform()
			{
				ScaleX = scaleX,
				ScaleY = scaleY
			};
		}

		private int width = -1;
		private int height = -1;
		private int targetFramesPerSecond = -1;

		private System.Windows.Threading.DispatcherTimer timer;

		public int TargetFramesPerSecond
		{
			get { return this.targetFramesPerSecond; }
			set
			{
				if (this.targetFramesPerSecond == -1)
				{
					this.targetFramesPerSecond = System.Math.Max(0, System.Math.Min(60, value));
				}
				else
				{
					throw new InvalidOperationException("Cannot change the frames per second once it has been set");
				}
			}
		}

		public int GameWidth
		{
			get { return this.width; }
			set
			{
				if (this.width == -1)
				{
					this.width = value;
				}
				else
				{
					throw new InvalidOperationException("Cannot change the GameWidth once it has already been set.");
				}
			}
		}

		public int GameHeight
		{
			get { return this.height; }
			set
			{
				if (this.height == -1)
				{
					this.height = value;
				}
				else
				{
					throw new InvalidOperationException("Cannot change the GameHeight once it has already been set.");
				}
			}
		}

		private WriteableBitmap finalScreen = null;

		private void InitializeGame()
		{
			this.GameScreen = new Graphics.Image(new WriteableBitmap(this.width, this.height));

			this.GameScreenImageControl.Source = this.StretchUsingNearestNeighbor
				? this.finalScreen
				: this.GameScreen.Bmp;

			this.activeScene = this.startingScene;

			if (this.targetFramesPerSecond == 60)
			{
				CompositionTarget.Rendering += new EventHandler(timer_Tick);
			}
			else
			{
				int millisecondsPerFrame = System.Math.Max(1, (int)(1000 / this.targetFramesPerSecond));

				this.timer = new System.Windows.Threading.DispatcherTimer();
				this.timer.Interval = new TimeSpan(0, 0, 0, 0, millisecondsPerFrame);
				this.timer.Tick += new EventHandler(timer_Tick);

				this.timer.Start();
			}

		}

		private int gameCounter = 0;

		public Input.Key FullScreenShortcutKey { get; set; }

		private bool IsFullScreen
		{
			get
			{
				return Application.Current.Host.Content.IsFullScreen;
			}
			set
			{
				if (this.IsFullScreen != value)
				{
					Application.Current.Host.Content.IsFullScreen = value;
				}
			}
		}

		private void timer_Tick(object sender, EventArgs e)
		{
			if (!this.activeScene.Initialized)
			{
				this.activeScene.DoInitialize();
				this.activeScene.Initialized = true;
			}

			if (Sound.SoundManager.Instance != null)
			{
				Sound.SoundManager.Instance.Update();
			}
			this.activeScene.DoProcessInput(Input.InputManager.GetAndClearEvents());
			this.activeScene.DoUpdate(this.gameCounter);
			this.activeScene.DoRender(this.GameScreen);

			GameSceneBase newGameScene = this.activeScene.SwitchTo ?? this.activeScene;
			if (newGameScene != this.activeScene)
			{
				this.activeScene = newGameScene;
				Graphics.Text.ClearTextCache();
			}

			this.gameCounter++;
			this.Flip();
		}

		private int[] xTargets = new int[0];
		private int[] yTargets = new int[0];
		private int xTargetLength = 0;
		private int yTargetLength = 0;

		private void InvalidateFinalScreenIfNecessary()
		{
			int actualWidth = (int)this.ActualWidth;
			int actualHeight = (int)this.ActualHeight;
			int gameWidth = this.GameWidth;
			int gameHeight = this.GameHeight;
			int x, y;

			if (actualWidth > 0 && actualHeight > 0)
			{
				if (this.finalScreen == null ||
					this.finalScreen.PixelWidth != actualWidth ||
					this.finalScreen.PixelHeight != actualHeight)
				{
					this.finalScreen = new WriteableBitmap(actualWidth, actualHeight);
					this.GameScreenImageControl.Source = this.finalScreen;
					int[] xTargets = this.xTargets;
					int[] yTargets = this.yTargets;

					if (this.xTargetLength < actualWidth)
					{
						xTargets = new int[actualWidth];
						this.xTargets = xTargets;
					}

					if (this.yTargetLength < actualHeight)
					{
						yTargets = new int[actualHeight];
						this.yTargets = yTargets;
					}

					for (x = 0; x < actualWidth; ++x)
					{
						xTargets[x] = gameWidth * x / actualWidth;
					}

					for (y = 0; y < actualHeight; ++y)
					{
						yTargets[y] = gameHeight * y / actualHeight;
					}
				}

				int[] finalPixels = this.finalScreen.Pixels;
				int[] sourcePixels = this.GameScreen.Bmp.Pixels;

				int value;
				int breakValue = Gamelight.Graphics.Colors.Black.IntValue;
				for (y = 0; y < actualHeight; ++y)
				{
					for (x = 0; x < actualWidth; ++x)
					{
						value = sourcePixels[xTargets[x] + yTargets[y] * gameWidth];
						if (value != breakValue)
						{

						}
						finalPixels[actualWidth * y + x] = value;
					}
				}
				this.finalScreen.Invalidate();
			}
		}

		private void Flip()
		{
			if (this.StretchUsingNearestNeighbor)
			{
				this.InvalidateFinalScreenIfNecessary();
				this.finalScreen.Invalidate();
			}
			else
			{
				this.GameScreen.Bmp.Invalidate();
			}
		}

		private void layoutRoot_KeyUp(object sender, KeyEventArgs e)
		{
			Input.InputManager.KeyUp(e.Key, e.PlatformKeyCode);
		}

		private void layoutRoot_KeyDown(object sender, KeyEventArgs e)
		{
			Input.Key key = Input.InputManager.KeyDown(e.Key, e.PlatformKeyCode);
			if (this.FullScreenShortcutKey != Input.Key.None &&
				key == this.FullScreenShortcutKey)
			{
				bool doScaling = !this.IsFullScreen;

				this.IsFullScreen = true;
				if (doScaling)
				{

				}
			}
		}

		private void layoutRoot_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
		{
			Point p = TransformPoint(e);
			Input.InputManager.MouseUp(false, (int)p.X, (int)p.Y);
		}

		private void layoutRoot_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
		{
			Point p = TransformPoint(e);
			Input.InputManager.MouseDown(false, (int)p.X, (int)p.Y);
		}

		private void layoutRoot_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
		{
			Point p = TransformPoint(e);
			Input.InputManager.MouseUp(true, (int)p.X, (int)p.Y);
		}

		private void layoutRoot_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			Point p = TransformPoint(e);
			Input.InputManager.MouseDown(true, (int)p.X, (int)p.Y);
		}

		private void layoutRoot_MouseMove(object sender, MouseEventArgs e)
		{
			Point p = TransformPoint(e);
			Input.InputManager.MouseMove((int)p.X, (int)p.Y);
		}

		private void layoutRoot_MouseLeave(object sender, MouseEventArgs e)
		{
			Point p = TransformPoint(e);
			Input.InputManager.MouseLeave((int)p.X, (int)p.Y);
		}

		private void layoutRoot_MouseEnter(object sender, MouseEventArgs e)
		{
			Point p = TransformPoint(e);
			Input.InputManager.MouseEnter((int)p.X, (int)p.Y);
		}

		private Point TransformPoint(MouseEventArgs e)
		{
			if (this.GameScreen != null)
			{
				Point p = e.GetPosition(this.GameScreenImageControl);
				double visualWidth = this.GameScreenImageControl.ActualWidth;
				double visualHeight = this.GameScreenImageControl.ActualHeight;
				double gameWidth = this.GameScreen.Width;
				double gameHeight = this.GameScreen.Height;

				return new Point(p.X * gameWidth / visualWidth + 0.5, p.Y * gameHeight / visualHeight + 0.5);
			}
			else
			{
				return new Point(0, 0);
			}
		}
	}
}
